﻿#define DEBUG_MSGx

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using TTEngine;
using SCG.General;
using SCG.TurboSprite;

//TODO drop in same space when click Drop in context menu
//TODO throw should display message
//TODO escort should display message

namespace TorchesTombs
{
    public partial class DungeonPage : BasePage, IDungeonHost
    {
        //constructor
        public DungeonPage()
        {
            InitializeComponent();
        }

        //the dungeon level
        public DungeonLevel Level
        {
            get
            {
                return _level;
            }
            set
            {
                _level = value;
                surface.Level = value;
                if (_level != null)
                {
                    surface.CellsX = _level.Width;
                    surface.CellsY = _level.Height;
                }
            }
        }

        //who's selected?
        public Actor SelectedActor { get; set; }

        //page loaded/unloaded
        public override void PageLoaded()
        {
            timerMain.Start();
            surface.Active = true;
        }
        public override void PageUnloaded()
        {
            timerMain.Stop();
            surface.Active = false;
        }

        private void surface_ActorSelected(object sender, ActorEventArgs e)
        {
            Actor a = e.Actor;
            if (a.IsPC)
            {
                UpdateActor(a);
                SelectedActor = a;
            }
#if DEBUG_MSG
            txtLog.Text += "Actor Selected: " + e.Actor.ObjectName + Environment.NewLine;
            txtLog.Refresh();
#endif
        }

        //update selected actor UI
        public void UpdateActor(Actor actor)
        {
            lblName.Text = actor.DisplayLabel;
            lblName.Refresh();
            lvGear.Items.Clear();
            if (actor.Gear != null)
            {
                foreach (MapObject mo in actor.Gear)
                {
                    //gear name
                    ListViewItem lvi = lvGear.Items.Add(mo.DisplayLabel);

                    //gear image
                    if (!_gearImageIndices.ContainsKey(mo.ObjectName))
                    {
                        Bitmap bmp = new Bitmap(32, 32);
                        Graphics g = Graphics.FromImage(bmp);
                        using (g)
                        {
                            Bitmap bmpGear = mo.Image;
                            g.DrawImage(bmpGear, 0, 0, 32, 32);
                        }
                        imgGear.Images.Add(bmp);
                        _gearImageIndices[mo.ObjectName] = imgGear.Images.Count - 1;
                    }
                    lvi.ImageIndex = _gearImageIndices[mo.ObjectName];

                    //hands icon for readying
                    lvi.Tag = mo;
                    if (mo.HandsRequired == 2)
                    {
                        if (actor.HasReadiedGearWithTag(mo.ObjectName))
                            lvi.StateImageIndex = 3;
                        else
                            lvi.StateImageIndex = 1;
                    }
                    else if (mo.HandsRequired == 1)
                    {
                        if (actor.HasReadiedGearWithTag(mo.ObjectName))
                            lvi.StateImageIndex = 2;
                        else
                            lvi.StateImageIndex = 0;
                    }
                }
            }
            lvGear.Refresh();
        }

        //clean up
        public void Cleanup()
        {
            surface.Cleanup();
        }

        //impulse processed
        public void ImpulseProcessed()
        {
            surface.ImpulseProcessed();
        }

        //center on a cell
        public void CenterOn(int x, int y)
        {
            surface.CenterOnCell(x, y);
        }

        private void surface_CellClicked(object sender, SCG.TurboSprite.SquareGrid.CellEventArgs e)
        {
#if DEBUG_MSG
            txtLog.Text += "Cell Clicked: " + e.X + "," + e.Y + Environment.NewLine;
            txtLog.Refresh();
#endif
        }

        private void surface_MapObjectsClicked(object sender, MapObjectListEventArgs e)
        {
            MapObject moFirst = e.MapObjects[0];

#if DEBUG_MSG
            txtLog.Text += "MapObjectsClicked:" + Environment.NewLine;
            foreach (MapObject mo in e.MapObjects)
                txtLog.Text += mo.ObjectName + Environment.NewLine;
            txtLog.Refresh();
#endif

            //compose the popup menu
            CleanupActionPopup();
            bool spaceBlocked = false;
            foreach (MapObject mo in e.MapObjects)
            {
                if (mo.BlocksMovement)
                    spaceBlocked = true;
                List<string> actions = mo.GetActionsAdjacent(SelectedActor);
                foreach (string action in actions)
                {
                    ActionMenuItem ami = new ActionMenuItem(mo, action);
                    ami.BackColor = popupMapObjects.BackColor;
                    ami.ForeColor = lblName.ForeColor;
                    ami.Quantity = mo.Quantity;
                    popupMapObjects.Items.Add(ami);
                    ami.Click += tmi_Click;

                    //add a quantity menu item?
                    if (action == "Take")
                        if (mo.Quantity > 1)
                        {
                            ItemQuantityMenuItem iqm = new ItemQuantityMenuItem(ami);
                            popupMapObjects.Items.Add(iqm);
                            ami.UpdateText();
                        }
                }
            }

            //add the "move here" item
            if (!spaceBlocked && popupMapObjects.Items.Count > 0)
            {
                if (!moFirst.Position.EqualsPoint(SelectedActor.Position))
                {
                    if (_tmiMoveHere == null)
                    {
                        _tmiMoveHere = new ToolStripMenuItem("Move Here");
                        _tmiMoveHere.BackColor = popupMapObjects.BackColor;
                        _tmiMoveHere.ForeColor = lblName.ForeColor;
                        _tmiMoveHere.Click += tmiMoveHere_Click;
                    }
                    _tmiMoveHere.Tag = new SCGPoint(moFirst.X, moFirst.Y);

                    popupMapObjects.Items.Add(_tmiMoveHere);
                }
            }

            //show popup
            if (popupMapObjects.Items.Count > 0)
            {
                int x = moFirst.X * 64 - surface.OffsetX + MainForm.Location.X;
                int y = moFirst.Y * 64 - surface.OffsetY + MainForm.Location.Y;
                x += surface.Left;
                y += surface.Top;
                if (popupMapObjects.Items.Count > 0)
                    popupMapObjects.Show(new Point(x, y));
            }
        }

        private void surface_SpriteClicked(object sender, SCG.TurboSprite.SpriteClickEventArgs e)
        {
#if DEBUG_MSG
            MapObjectSprite mos = e.Sprite as MapObjectSprite;
            txtLog.Text += "Sprite Clicked: " + (mos.MapObject.ObjectName) + Environment.NewLine;
            txtLog.Refresh();
#endif
        }

        //move here menu item
        private void tmiMoveHere_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem tmi = (ToolStripMenuItem)sender;
            SCGPoint pt = (SCGPoint)tmi.Tag;
            SelectedActor.DestX = pt.X;
            SelectedActor.DestY = pt.Y;
            SelectedActor.Orders = OrderType.Move;
            surface.SelectedActor = null;
        }

        //popup menu click event
        private void tmi_Click(object sender, EventArgs e)
        {
            ActionMenuItem ami = (ActionMenuItem)sender;
            MapObject mo = ami.MapObject;
            if (surface.SelectedActor == null)
                return;
            mo.ProcessAdjacentAction(SelectedActor, ami.Action, ami.Quantity);
            surface.SelectedActor = null;
#if DEBUG_MSG
            txtLog.Text += "Selected action: " + ami.Action + " for Object: " + mo.ObjectName + Environment.NewLine;
            txtLog.Refresh();
#endif
        }

        //main impulse processing loop
        private void timerMain_Tick(object sender, EventArgs e)
        {
            Invoke((MethodInvoker)
            (() =>
            {
                Level.ProcessImpulse();
                ImpulseProcessed();
                lblImpulse.Text = "Impulse: " + Level.Impulse;
                lblImpulse.Refresh();
            }
            ));
        }

        //clicked on gear list
        private void lvGear_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.X > 38)
                return;
            ListViewItem lvi = lvGear.GetItemAt(e.X, e.Y);
            if (lvi != null)
            {
                MapObject mo = (MapObject)lvi.Tag;
                if (SelectedActor.HasReadiedGearWithTag(mo.ObjectName))
                    SelectedActor.UnreadyGear(mo);
                else
                    SelectedActor.ReadyGear(mo);
                UpdateActor(SelectedActor);
            }
        }

        //clicked a piece of gear, show menu
        private void lvGear_SelectedIndexChanged(object sender, EventArgs e)
        {
            lvGear.Refresh();
            if (lvGear.SelectedItems.Count == 0)
            {
                surface.ClearDropTargets();
                return;
            }

            //obtain gear item
            ListViewItem lvi = lvGear.SelectedItems[0];
            MapObject mo = (MapObject)lvi.Tag;

            //get actions
            CleanupActionPopup();
            List<string> actions = mo.GetGearActions(SelectedActor);
            foreach (string action in actions)
            {
                ActionMenuItem ami = new ActionMenuItem(mo, action);
                ami.IsGearItem = true;
                ami.Quantity = mo.Quantity;
                ami.BackColor = popupMapObjects.BackColor;
                ami.ForeColor = lblName.ForeColor;
                ami.UpdateText();
                popupMapObjects.Items.Add(ami);
                //TODO add handler

                //drop quantity
                if (action == "Drop")
                {
                    _dropMenu = ami;
                    if (ami.Quantity > 1)
                    {
                        ItemQuantityMenuItem qmi = new ItemQuantityMenuItem(ami);
                        popupMapObjects.Items.Add(qmi);
                        ami.UpdateText();
                    }

                    //throw
                    _throwing = false;
                    if (SelectedActor.CanThrow(mo))
                    {
                        ItemThrowMenuItem throwItem = new ItemThrowMenuItem(ami, this);
                        throwItem.ForeColor = lblName.ForeColor;
                        popupMapObjects.Items.Add(throwItem);
                    }
                }
            }

            //show the popup
            if (popupMapObjects.Items.Count > 0)
            {
                Point pt = lvGear.PointToScreen(lvi.Position);
                pt.X += lvGear.Width - 42;
                popupMapObjects.Show(pt);
            }

            //and show drop targets
            surface.CreateDropTargets(SelectedActor.X, SelectedActor.Y);
        }

        //clean up the popup menu contents
        private void CleanupActionPopup()
        {
            foreach (ToolStripItem ti in popupMapObjects.Items)
            {
                if (ti is ActionMenuItem)
                {
                    ActionMenuItem ami = (ActionMenuItem)ti;
                    if (ami.IsGearItem)
                    {//TODO remove handler
                    }
                    else
                        ami.Click -= tmi_Click;
                }
                else if (ti is ItemQuantityMenuItem)
                {
                    ItemQuantityMenuItem iqm = (ItemQuantityMenuItem)ti;
                    iqm.UnHook();
                }
                else if (ti is ItemThrowMenuItem)
                {
                    ItemThrowMenuItem itm = (ItemThrowMenuItem)ti;
                    itm.UnHook();
                }
            }
            popupMapObjects.Items.Clear();
        }

        //show drop targets for dropping and throwing modes
        public void CreateDropTargets(MapObject mo, bool throwingMode)
        {
            _throwing = throwingMode;
            if (throwingMode)
            {
                RayTracer rt = new RayTracer(Level);
                List<SCGPoint> pts = rt.RayTrace(SelectedActor.X, SelectedActor.Y, SelectedActor.ThrowRange(mo));
                pts.RemovePoint(SelectedActor.Position);

                //remove wall points
                List<SCGPoint> openPts = new List<SCGPoint>();
                foreach (SCGPoint pt in pts)
                    if (Level.IsOpenTerrain(pt.X, pt.Y, true))
                        if (Level.VisibilityMatrix.Get(pt.X, pt.Y))
                            if (Level.IlluminationMatrix.Get(pt.X, pt.Y) > 0)
                                openPts.Add(pt);

                surface.CreateDropTargets(openPts);
            }
            else
                surface.CreateDropTargets(SelectedActor.X, SelectedActor.Y);
        }
        
        //drop target
        private void surface_DropTargetClicked(object sender, PointEventArgs e)
        {
#if DEBUG_MSG
            txtLog.Text += "DropTarget: " + e.Point.X + "," + e.Point.Y + Environment.NewLine;
            txtLog.Refresh();
#endif
            //process the drop command
            MapObject mo = _dropMenu.MapObject;

            //first see if they threw it
            if (_throwing)
            {
                _throwing = false;

                //throwing the last one?
                MapObject thrownObject = null;
                if (mo.Quantity == 1)
                {
                    thrownObject = mo;
                    SelectedActor.RemoveGear(mo);
                }
                else
                {
                    //create a qty 1 copy, deduct one
                    thrownObject = mo.Clone();
                    thrownObject.Quantity = 1;
                    mo.Quantity--;
                }

                //tell the sprite surface to throw this object, upon arrival it will callback to process the impact
                surface.ThrowObject(thrownObject, SelectedActor.X, SelectedActor.Y, e.Point.X, e.Point.Y);

                UpdateActor(SelectedActor);
                surface.ClearDropTargets();
                surface.SelectedActor = null;
                return;
            }

            //normal drop
            Actor reciever = null;
            MapObject dropped = mo;
            if (_dropMenu.Quantity == mo.Quantity)
            {
                //dropping all
                SelectedActor.RemoveGear(mo);
                UpdateActor(SelectedActor);
                reciever = Level.GetActorAt(e.Point.X, e.Point.Y, true);
                if (reciever != null)
                    reciever.AddGear(mo);
                else
                    Level.AddObjectToMap(mo, e.Point.X, e.Point.Y);
            }
            else
            {
                //dropping some
                dropped = mo.Clone();
                dropped.Quantity = _dropMenu.Quantity;
                reciever = Level.GetActorAt(e.Point.X, e.Point.Y, true);
                if (reciever != null)
                    reciever.AddGear(dropped);
                else
                    Level.AddObjectToMap(dropped, e.Point.X, e.Point.Y);
                mo.Quantity -= dropped.Quantity;
            }
            if (reciever == null)
                Level.Host.DisplayMessage(SelectedActor.DisplayName + " drops the " + dropped.DisplayLabel + ".");
            else
                Level.Host.DisplayMessage(SelectedActor.DisplayName + " gives the " + dropped.DisplayLabel + " to " + reciever.DisplayName + ".");
            UpdateActor(SelectedActor);
            surface.ClearDropTargets();
            surface.SelectedActor = null;
        }

        #region IDungeonHost

        public void ActorMoved(Actor actor, int oldX, int oldY)
        {
            MapObjectSprite mos = (MapObjectSprite)actor.Sprite;
            mos.Facing = actor.X < oldX ? Facing.Left : Facing.Right;
            surface.MoveSprite(mos, actor.X, actor.Y, 10);
        }

        public void ActorProne(Actor actor)
        {
            MapObjectSprite mos = (MapObjectSprite)actor.Sprite;
            mos.Mode = MoveMode.Prone;
        }

        public void ActorRises(Actor actor)
        {
            MapObjectSprite mos = (MapObjectSprite)actor.Sprite;
            mos.Mode = MoveMode.Normal;
        }

        public void SpriteAdded(MapObject mo)
        {
            surface.SpriteAdded(mo);
        }

        public void SpriteRemoved(MapObject mo)
        {
            surface.SpriteRemoved(mo);
        }

        public void SpriteCleanup()
        {
            surface.Cleanup();
        }

        public void DisplayMessage(string msg)
        {
            if (txtLog.InvokeRequired)
            {
                Invoke((MethodInvoker)
                (() =>
                {
                    DisplayMessageThreadSafe(msg);
                }
                ));
            }
            else
                DisplayMessageThreadSafe(msg);
        }
        private void DisplayMessageThreadSafe(string msg)
        {
            txtLog.AppendText(msg + Environment.NewLine);
            //txtLog.Text += msg + Environment.NewLine;
            txtLog.Refresh();
        }

        public void DisplayMessage(string msg, int x, int y)
        {
            if (Level.VisibilityMatrix.Get(x, y))
                if (Level.IlluminationMatrix.Get(x, y) > 0)
                    DisplayMessage(msg);
        }

        public void DisplayMessage(string msg, Actor actor)
        {
            if (actor.IsPC)
                DisplayMessage(msg);
        }

        public void ActorUpdated(Actor actor)
        {
            if (SelectedActor == actor)
                UpdateActor(actor);
        }

        public void SpriteExplodes(MapObject mo, int x, int y)
        {
            if (Level.VisibilityMatrix.Get(x, y))
                surface.SpriteExplodes(mo, x, y);
        }

        public void BloodSplatter(int x, int y)
        {
            if (Level.VisibilityMatrix.Get(x, y))
                surface.BloodSplatter(x, y);
        }

        public void LungeSprite(MapObject mo, MapObject target)
        {
            if (Level.VisibilityMatrix.Get(mo.X, mo.Y))
                surface.LungeSprite(mo, target);
        }

        public void ShuffleSprite(MapObject mo)
        {
            surface.ShuffleSprite(mo);
        }

        public void ShuffleSprite(MapObject mo, int x, int y)
        {
            surface.ShuffleSprite(mo, x, y);
        }

        #endregion

        //private members
        private ToolStripMenuItem _tmiMoveHere = null;
        private DungeonLevel _level;
        private Dictionary<string, int> _gearImageIndices = new Dictionary<string, int>();
        private ActionMenuItem _dropMenu = null;
        private bool _throwing = false;

        private void button1_Click(object sender, EventArgs e)
        {
            int x = Level.Rooms[0].CenterX;
            int y = Level.Rooms[0].CenterY;
            surface.BloodSplatter(x + Globals.RandomIntFromTo(-1, 1), y + Globals.RandomIntFromTo(-1, 1));
        }
    }
}
